#include "common/constant.h"
#include "common/exception.h"
#include "executables_handler.h"
#include "logger.h"
#include "nosal/log/logger.h"
#include "nosal/log/logging.h"

namespace
{
netaos::nosal::log::Logger& GetLogger()
{
    static netaos::nosal::log::Logger& logger
        = netaos::nosal::log::CreateLogger("ExHa", "Executables Handler", netaos::nosal::exec::internal::kLogLevel);
    return logger;
}
}  // namespace

namespace netaos {
namespace nosal {
namespace exec {
namespace internal {

ExecutablesHandler::ExecutablesHandler(std::map<std::string, FunctionGroup>& list_of_function_groups,
    ExecutablesParser&& executables_parser,
    std::vector<std::string> machine_specific_environment_variables)
    : list_of_function_groups_(list_of_function_groups)
    , executables_parser_(std::move(executables_parser))
    , machine_specific_environment_variables_(machine_specific_environment_variables)
{ }

std::vector<Executable>& ExecutablesHandler::GetListOfExecutables() noexcept
{
    return list_of_executables_;
}

netaos::nosal::core::Result<void> ExecutablesHandler::ParseExecutionManifests()
{
    GetLogger().LogInfo() << "Parsing the Execution Manifests started.";

    netaos::nosal::core::Result<std::vector<Executable>> list_of_executables_result
        = std::move(executables_parser_.ParseDirectories());
    if (!list_of_executables_result) {
        return netaos::nosal::core::Result<void>{list_of_executables_result.Error()};
    }

    list_of_executables_ = std::move(list_of_executables_result).Value();

    for (auto& executable : list_of_executables_) {
        ExtractStartupConfigurationsFromManifest(executable);
    }

    GetLogger().LogInfo() << "Parsing of Execution Manifests finished.";

    return netaos::nosal::core::Result<void>{};
}

netaos::nosal::core::Result<void> ExecutablesHandler::ReparseExecutionManifests()
{
    GetLogger().LogInfo() << "Reparsing Execution Manifests started.";

    netaos::nosal::core::Result<std::vector<Executable>> list_of_new_executablesResult
        = std::move(executables_parser_.ParseDirectories());
    if (!list_of_new_executablesResult) {
        return netaos::nosal::core::Result<void>{list_of_new_executablesResult.Error()};
    }

    std::vector<Executable> list_of_new_executables = std::move(list_of_new_executablesResult).Value();

    netaos::nosal::core::Result<void> reparsing_result = CheckForRemovedExecutables(list_of_new_executables);
    if (!reparsing_result) {
        GetLogger().LogError() << "Reparsing Execution Manifests failed during Check for Removed Executables.";
    } else {
        CheckForNewInstallations(list_of_new_executables);

        reparsing_result = CheckForUpdatedExecutables(list_of_new_executables);

        if (!reparsing_result) {
            GetLogger().LogError() << "Reparsing Execution Manifests failed during Update of Executables.";
        } else {
            GetLogger().LogInfo() << "Reparsing Execution Manifests successfully finished..";
        }
    }

    return reparsing_result;
}

void ExecutablesHandler::AssignStartupConfigurationToFunctionGroup(
    std::shared_ptr<StartupConfiguration> startup_configuration,
    const config::StartupConfiguration& config_in_manifest)
{
    if (startup_configuration->GetFunctionClusterAffiliation() == kStateManagementFunctionClusterAffiliation) {
        GetLogger().LogInfo() << "Process with FunctionClusterAffiliation" << kStateManagementFunctionClusterAffiliation
                              << "found";

        if (config_in_manifest.GetFunctionGroupStates().size() != 0) {
            GetLogger().LogError() << "Misconfigured State Management process. State Management must not run in other "
                                      "FunctionGroups than the MachineFG. Skipping this configuration.";
            return;
        }

        for (const auto& state : list_of_function_groups_.at(kMachineFunctionGroupName).GetAllStates()) {
            if (state.GetName() == kStateOff) {
                continue;
            }
            list_of_function_groups_.at(kMachineFunctionGroupName).AddStartupConfig(state.GetName(), startup_configuration);
            GetLogger().LogDebug() << "StartupConfiguration for State Management in MachineFG State" << state.GetName()
                                   << "added.";
        }
    } else {
        const auto& machineFgStates = config_in_manifest.GetMachineStates();
        const auto& functionGroupStates = config_in_manifest.GetFunctionGroupStates();

        if (!machineFgStates.empty()) {
            auto& machineFgStatesGroup = list_of_function_groups_.at(kMachineFunctionGroupName);

            for (const auto& state : machineFgStates) {
                machineFgStatesGroup.AddStartupConfig(state, startup_configuration);
            }
        }

        for (const auto& state : functionGroupStates) {
            auto groupPosition = list_of_function_groups_.find(state.name);
            if (groupPosition == list_of_function_groups_.end()) {
                GetLogger().LogError()
                    << "Process" << startup_configuration->GetName()
                    << "is assigned to a Function Group that is not contained in the Machine Manifest. Skipping.";
                continue;
            }
            auto& functionGroup = groupPosition->second;
            functionGroup.AddStartupConfig(state.state, startup_configuration);
        }
    }
}

void ExecutablesHandler::CheckForNewInstallations(std::vector<Executable>& list_of_new_executables)
{
    auto it = list_of_new_executables.begin();
    while (it != list_of_new_executables.end()) {
        auto execIterator = std::find_if(list_of_executables_.begin(),
            list_of_executables_.end(),
            [it](const Executable& executable) { return executable.executable_name_ == (*it).executable_name_; });
        if (execIterator == list_of_executables_.end()) {
            GetLogger().LogInfo() << "Exectuable" << (*it).executable_name_ << "was newly installed on the image.";
            list_of_executables_.push_back(std::move(*it));
            it = list_of_new_executables.erase(it);
            ExtractStartupConfigurationsFromManifest(list_of_executables_.back());
        } else {
            it++;
        }
    }
}

netaos::nosal::core::Result<void> ExecutablesHandler::CheckForRemovedExecutables(
    const std::vector<Executable>& list_of_new_executables)
{
    auto it = list_of_executables_.begin();
    while (it != list_of_executables_.end()) {
        auto listIterator = std::find_if(list_of_new_executables.begin(),
            list_of_new_executables.end(),
            [it](const Executable& exec) { return exec.executable_name_ == (*it).executable_name_; });
        if (listIterator == list_of_new_executables.end()) {
            GetLogger().LogInfo() << "Executable" << (*it).executable_name_ << "shall be removed from the image.";

            for (auto& groupMapElement : list_of_function_groups_) {
                FunctionGroup& group = groupMapElement.second;
                if (group.IsStartupConfigurationPartOfGroup((*it).executable_name_)) {
                    if (group.GetCurrentStateName() != kStateOff) {
                        GetLogger().LogError() << "Trying to remove application" << (*it).executable_name_
                                               << "but FunctionGroup" << group.GetName() << "is not in State Off.";
                        return netaos::nosal::core::Result<void>{ExecManifestParsingErrc::kFunctionGroupNotInStateOff};
                    }
                    group.RemoveStartupConfigFromGroup((*it).executable_name_);
                }
            }

            it = list_of_executables_.erase(it);
        } else {
            it++;
        }
    }
    return netaos::nosal::core::Result<void>{};
}

netaos::nosal::core::Result<void> ExecutablesHandler::CheckForUpdatedExecutables(
    std::vector<Executable>& list_of_new_executables)
{
    auto new_executables_it = list_of_new_executables.begin();
    while (new_executables_it != list_of_new_executables.end()) {
        auto executablesIt = std::find_if(
            list_of_executables_.begin(), list_of_executables_.end(), [new_executables_it](const Executable& executable) {
                return executable.executable_name_ == (*new_executables_it).executable_name_;
            });
        if (executablesIt == list_of_executables_.end()) {
            GetLogger().LogError() << "Executable" << (*new_executables_it).executable_name_ << "to be updated not found.";
            return netaos::nosal::core::Result<void>{ExecManifestParsingErrc::kExecutableNotFound};
        }
        const bool kHasNewManifestVersion
            = (*new_executables_it).manifest_.GetExecutableVersion() != (*executablesIt).manifest_.GetExecutableVersion()
            || (*new_executables_it).manifest_.GetCrc32Value() != (*executablesIt).manifest_.GetCrc32Value()
            || (*new_executables_it).executable_dir_ != (*executablesIt).executable_dir_;
        if (kHasNewManifestVersion) {
            GetLogger().LogInfo() << "Executable" << (*new_executables_it).executable_name_ << "shall be updated.";

            for (auto& groupMapPos : list_of_function_groups_) {
                FunctionGroup& group = groupMapPos.second;
                if (group.IsStartupConfigurationPartOfGroup((*new_executables_it).executable_name_)) {
                    if (group.GetCurrentStateName() != kStateOff) {
                        GetLogger().LogError() << "Trying to update Executable" << (*new_executables_it).executable_name_
                                               << "but the FunctionGroup" << group.GetName() << "is not in state Off.";
                        return netaos::nosal::core::Result<void>{ExecManifestParsingErrc::kFunctionGroupNotInStateOff};
                    } else {
                        // perform the actual update
                        (*executablesIt).executable_path_ = (*new_executables_it).executable_path_;
                        (*executablesIt).executable_dir_ = (*new_executables_it).executable_dir_;
                        (*executablesIt).manifest_ = std::move((*new_executables_it).manifest_);
                        group.RemoveStartupConfigFromGroup((*new_executables_it).executable_name_);
                        (*executablesIt).startup_configurations_ = std::vector<std::shared_ptr<StartupConfiguration>>{};
                        ExtractStartupConfigurationsFromManifest(*executablesIt);
                    }
                }
            }
            new_executables_it = list_of_new_executables.erase(new_executables_it);
        } else {
            new_executables_it++;
        }
    }

    return netaos::nosal::core::Result<void>{};
}

void ExecutablesHandler::ExtractStartupConfigurationsFromManifest(Executable& executable)
{
    config::ExecutionManifest& execution_manifest = executable.manifest_;
    auto& startupConfigsInManifest = execution_manifest.GetStartupConfigs();

    std::vector<std::shared_ptr<StartupConfiguration>> startup_configurations_per_executable;
    startup_configurations_per_executable.reserve(startupConfigsInManifest.size());

    for (const auto& config_in_manifest : startupConfigsInManifest) {
        startup_configurations_per_executable.emplace_back(
            std::make_shared<StartupConfiguration>(executable.executable_dir_,
                executable.executable_path_,
                executable.executable_name_,
                execution_manifest,
                config_in_manifest,
                machine_specific_environment_variables_));

        AssignStartupConfigurationToFunctionGroup(startup_configurations_per_executable.back(), config_in_manifest);
    }

    executable.startup_configurations_ = std::move(startup_configurations_per_executable);
    return;
}

}  // namespace internal
}  // namespace exec
}  // namespace nosal
}  // namespace netaos
